///\todo upgrade

//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef _BUOLA_MAT_C2DTRANSFORM_H_
#define _BUOLA_MAT_C2DTRANSFORM_H_

#include <buola/mat.h>
#include <buola/utility/csize.h>

namespace buola { namespace mat {

inline CMat23d operator*(const CMat23d &pLH,const CMat23d &pRH)
{
    return {pLH(0,0)*pRH(0,0)+pLH(0,1)*pRH(1,0),
                                pLH(0,0)*pRH(0,1)+pLH(0,1)*pRH(1,1),
                                                    pLH(0,0)*pRH(0,2)+pLH(0,1)*pRH(1,2)+pLH(0,2),
            pLH(1,0)*pRH(0,0)+pLH(1,1)*pRH(1,0),
                                pLH(1,0)*pRH(0,1)+pLH(1,1)*pRH(1,1),
                                                    pLH(1,0)*pRH(0,2)+pLH(1,1)*pRH(1,2)+pLH(1,2)};
}

class C2DTransform
{
public:
    C2DTransform()
    {
        SetIdentity();
    }

    C2DTransform(const C2DTransform &pM)
        :   mF(pM.mF)
        ,   mB(pM.mB)
    {}

    bool IsIdentity() const
    {
        if(mF(0,0)!=1.0) return false;
        if(mF(0,1)!=0.0) return false;
        if(mF(0,2)!=0.0) return false;
        if(mF(1,0)!=0.0) return false;
        if(mF(1,1)!=1.0) return false;
        if(mF(1,2)!=0.0) return false;
        return true;
    }

    bool IsTranslation() const
    {
        if(mF(0,0)!=1.0) return false;
        if(mF(0,1)!=0.0) return false;
        if(mF(1,0)!=0.0) return false;
        if(mF(1,1)!=1.0) return false;
        return true;
    }

    void SetIdentity()
    {
        mF={1,0,0,
            0,1,0};
        mB={1,0,0,
            0,1,0};
    }

    void Translate(const CPoint_d &pT)
    {
        mF(0,2)+=mF(0,0)*pT.x+mF(0,1)*pT.y;
        mF(1,2)+=mF(1,0)*pT.x+mF(1,1)*pT.y;

        mB(0,2)-=pT.x;
        mB(1,2)-=pT.y;
    }

    void Scale(double pS)
    {
        mF(0,0)*=pS;
        mF(0,1)*=pS;
        mF(1,0)*=pS;
        mF(1,1)*=pS;

        mB*=1.0/pS;
    }

    void Scale(const CSize_d &pS)
    {
        mF(0,0)*=pS.x;
        mF(0,1)*=pS.y;
        mF(1,0)*=pS.x;
        mF(1,1)*=pS.y;

        mB(0,0)/=pS.x;
        mB(0,1)/=pS.x;
        mB(0,2)/=pS.x;
        mB(1,0)/=pS.y;
        mB(1,1)/=pS.y;
        mB(1,2)/=pS.y;
    }

    void Rotate(double pA)
    {
        double lSin,lCos;
        sincos(pA,&lSin,&lCos);

        double lF[4]={mF(0,0),mF(0,1),mF(1,0),mF(1,1)};

        mF(0,0)=lCos*lF[0]+lSin*lF[1];
        mF(0,1)=-lSin*lF[0]+lCos*lF[1];
        mF(1,0)=lCos*lF[2]+lSin*lF[3];
        mF(1,1)=-lSin*lF[2]+lCos*lF[3];

        //sincos of -pA
        lSin=-lSin;

        double lB[6]={mB(0,0),mB(0,1),mB(0,2),mB(1,0),mB(1,1),mB(1,2)};

        mB(0,0)=lCos*lB[0]-lSin*lB[3];
        mB(0,1)=lCos*lB[1]-lSin*lB[4];
        mB(0,2)=lCos*lB[2]-lSin*lB[5];
        mB(1,0)=lSin*lB[0]+lCos*lB[3];
        mB(1,1)=lSin*lB[1]+lCos*lB[4];
        mB(1,2)=lSin*lB[2]+lCos*lB[5];
    }

    C2DTransform &operator*=(const C2DTransform &pRH)
    {
        mF=mF*pRH.mF;
        mB=pRH.mB*mB;

        return *this;
    }

    C2DTransform &operator=(const C2DTransform &pRH)=default;

    double MaxScale() const
    {
        return std::max(std::abs(mF(0,0))+std::abs(mF(0,1)),std::abs(mF(1,0))+std::abs(mF(1,1)));
    }

    operator const mat::CMat23d&() const  {   return mF;        }
    const mat::CMat23d &F() const         {   return mF;        }
    const mat::CMat23d &I() const         {   return mB;        }
    double F(size_t r,size_t c) const     {   return mF(r,c);   }
    double I(size_t r,size_t c) const     {   return mB(r,c);   }

private:
    mat::CMat23d mF,mB;
};

/*namespace mat*/ } /* namespace buola*/ }

#endif
